﻿using UnityEngine;
using System.Collections;
using System;

using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace TcpConnector
{

    public struct TcpPckStruct
    {
        public int Type;
        public int Size;
        public byte[] Content;
    }

    public class Connector
    {
        const int HEAD_SIZE = 4;

        public TcpClient client { get; set; }

        protected bool connected;

        NetworkStream stream;// tcp stream 当前连接的网络流

        protected Exception exception;

        protected string _hostname = "localhost";
        protected int _port = 8124;
        protected int _timeout_milliseconds = 10000;

        // 申明委托事件
        public delegate void DelegateSocketMessage(TcpPckStruct data);

        public event DelegateSocketMessage SocketMessageEvent;

        private Connector() { }
        public static readonly Connector instance = new Connector();
      
        protected void BeginConnect()
        {
            try
            {
                client = new TcpClient(_hostname, _port);
                stream = client.GetStream();
                // record that it succeeded, for the main thread to return to the caller
                connected = true;
                Debug.Log("tcp connect");
            }
            catch (Exception ex)
            {
                // record the exception for the main thread to re-throw back to the calling code
                exception = ex;
            }
        }

        public TcpClient Connect()
        {    // kick off the thread that tries to connect
            connected = false;
            exception = null;
            Thread thread = new Thread(new ThreadStart(BeginConnect));
            thread.IsBackground = true; // So that a failed connection attempt  wont prevent the process from terminating while it does the long timeout
            thread.Name = "Tcpclient connect";
            thread.Start(); // wait for either the timeout or the thread to finish
            thread.Join(_timeout_milliseconds);

            if (connected == true)
            {       // it succeeded, so return the connection               
                thread.Abort();
                return client;
            }
            if (exception != null)
            {  // it crashed, so return the exception to the caller
                thread.Abort();
                throw exception;
            }
            else
            {    // if it gets here, it timed out, so abort the thread and throw an exception
                thread.Abort();
                string message = string.Format("TcpClient connection to {0}:{1} timed out",_hostname, _port);
                throw new TimeoutException(message);
            }
        }

        public void Disconnect()
        {
            stream.Close();
            client.Close();
        }

       // private byte[] receiveByte;// 接受的2进制byte
        /// <summary>
        /// tcpclient 异步读取
        /// NetworkStream的Read方法在关闭连接时会抛异常(IOException)。但是它会将线程阻塞。如果不想陷入阻塞状态，就只能通过上面的方法检测了！在读取网络流之前最好检测一下NetworkStream.DataAvailable有数据再读。
        /// </summary>
        public void Read()
        {
            
            if (TcpClientEx.IsOnline(client) && stream.DataAvailable)
            {
                AsyncCallback callBack = new AsyncCallback(onRead);
                byte[] receiveByte = new byte[client.ReceiveBufferSize];

                stream.BeginRead(receiveByte, 0, client.ReceiveBufferSize, new AsyncCallback(callBack), receiveByte);
            }
        }
        /// <summary>
        /// tcpclient 读取stream
        /// </summary>
        /// <param name="result"></param>
        private void onRead(IAsyncResult result)
        {
            stream.EndRead(result);
          
            byte[] receiveByte = result.AsyncState as byte[];

            byte[] msgTypeByte = new byte[HEAD_SIZE];

            int index = 0; // debug 0
            // 读取msg Type
            for (int i = 0; i < HEAD_SIZE; i++)
            {
                msgTypeByte[i] = receiveByte[i + index];
            }
            int msgType = BitConverter.ToInt32(msgTypeByte, 0);

            index += HEAD_SIZE;
            // 读取msg content 长度
            byte[] msgSizeByte = new byte[HEAD_SIZE];

            for (int i = 0; i < HEAD_SIZE; i++)
            {
                msgSizeByte[i] = receiveByte[i + index];
            }
            int msgSize = BitConverter.ToInt32(msgSizeByte, 0);
            index += HEAD_SIZE;
            // 读取msg content
            byte[] msgContentByte = new byte[msgSize];

            for (int i = 0; i < msgSize; i++)
            {
                msgContentByte[i] = receiveByte[i + index];
            }

            TcpPckStruct retData = new TcpPckStruct();
            retData.Type = msgType;
            retData.Size = msgSize;
            retData.Content = msgContentByte;

  

            if (this.SocketMessageEvent != null)
            {
                SocketMessageEvent(retData);
            }
        }

        /// <summary>
        /// tcpclient 写入stream 
        /// </summary>
        /// <param name="msgType"></param>
        /// <param name="msgContent"></param>
        public void Write(int msgType, byte[] msgContent)
        {
            byte[] msgTypeByte = BitConverter.GetBytes(msgType);
			//small endpoint to big endpoint
			Array.Reverse (msgTypeByte);
            int msgSize;
            if (msgContent != null)
            {
                msgSize = msgContent.Length;
            }
            else
            {
                msgSize = 0;
            }
            byte[] msgSizeByte = BitConverter.GetBytes(msgSize);
			Array.Reverse (msgSizeByte);
			//
            int totalSize = HEAD_SIZE + HEAD_SIZE + msgSize;
            byte[] msgByte = new byte[totalSize];
            int index = 0;

            for (int i = 0; i < HEAD_SIZE; i++)
            {
                msgByte[i] = msgTypeByte[i];
            }
            index += HEAD_SIZE;

            for (int i = 0; i < HEAD_SIZE; i++)
            {
                msgByte[index + i] = msgSizeByte[i];
            }
            index += HEAD_SIZE;
			//
            for (int j = 0; j < msgSize; j++)
            {
                msgByte[index + j] = msgContent[j];
            }

            if (stream.CanWrite)
            {         
                stream.BeginWrite(msgByte, 0, msgByte.Length, new AsyncCallback(SendAsyncCallback), null);
            }
        }

        private void SendAsyncCallback(IAsyncResult iAsyncResult)
        {
            stream.EndWrite(iAsyncResult);
            
            stream.Flush();
        }
    }
    /// <summary>
    /// TcpClient 连接状态检测
    /// TcpClient在连接成功后无法检测连接状态，即使对方关闭了网络连接。以下扩展可检测连接状态：
    /// </summary>
    public static class TcpClientEx
    {
        public static bool IsOnline(this TcpClient c)
        {
            return !((c.Client.Poll(1000, SelectMode.SelectRead) && (c.Client.Available == 0)) || !c.Client.Connected);
        }
    }
}
